/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "../sound/s_local.h"
#include "win_local.h"


typedef struct {
	ALCdevice *		hDevice;
	ALCcontext *	hALC;
} alwState_t;

static alwState_t	alwState;


/*
 ==================
 ALW_GetProcAddress
 ==================
*/
static void *ALW_GetProcAddress (const char *procName){

	void	*procAddress;

	procAddress = qalGetProcAddress(procName);
	if (!procAddress){
		ALW_Shutdown();

		Com_Error(true, "ALW_GetProcAddress: alGetProcAddress() failed for '%s'", procName);
	}

	return procAddress;
}

/*
 ==================
 ALW_InitExtensions
 ==================
*/
static void ALW_InitExtensions (void){

	Com_Printf("Initializing OpenAL extensions\n");

	if (qalcIsExtensionPresent(alwState.hDevice, "ALC_EXT_EFX")){
		alConfig.efxAvailable = true;

		qalcGetIntegerv(alwState.hDevice, ALC_MAX_AUXILIARY_SENDS, 1, &alConfig.maxAuxiliarySends);

		qalGenFilters = (ALGENFILTERS)ALW_GetProcAddress("alGenFilters");
		qalDeleteFilters = (ALDELETEFILTERS)ALW_GetProcAddress("alDeleteFilters");
		qalFilterf = (ALFILTERF)ALW_GetProcAddress("alFilterf");
		qalFilterfv = (ALFILTERFV)ALW_GetProcAddress("alFilterfv");
		qalFilteri = (ALFILTERI)ALW_GetProcAddress("alFilteri");
		qalFilteriv = (ALFILTERIV)ALW_GetProcAddress("alFilteriv");

		qalGenEffects = (ALGENEFFECTS)ALW_GetProcAddress("alGenEffects");
		qalDeleteEffects = (ALDELETEEFFECTS)ALW_GetProcAddress("alDeleteEffects");
		qalEffectf = (ALEFFECTF)ALW_GetProcAddress("alEffectf");
		qalEffectfv = (ALEFFECTFV)ALW_GetProcAddress("alEffectfv");
		qalEffecti = (ALEFFECTI)ALW_GetProcAddress("alEffecti");
		qalEffectiv = (ALEFFECTIV)ALW_GetProcAddress("alEffectiv");

		qalGenAuxiliaryEffectSlots = (ALGENAUXILIARYEFFECTSLOTS)ALW_GetProcAddress("alGenAuxiliaryEffectSlots");
		qalDeleteAuxiliaryEffectSlots = (ALDELETEAUXILIARYEFFECTSLOTS)ALW_GetProcAddress("alDeleteAuxiliaryEffectSlots");
		qalAuxiliaryEffectSlotf = (ALAUXILIARYEFFECTSLOTF)ALW_GetProcAddress("alAuxiliaryEffectSlotf");
		qalAuxiliaryEffectSlotfv = (ALAUXILIARYEFFECTSLOTFV)ALW_GetProcAddress("alAuxiliaryEffectSlotfv");
		qalAuxiliaryEffectSloti = (ALAUXILIARYEFFECTSLOTI)ALW_GetProcAddress("alAuxiliaryEffectSloti");
		qalAuxiliaryEffectSlotiv = (ALAUXILIARYEFFECTSLOTIV)ALW_GetProcAddress("alAuxiliaryEffectSlotiv");

		Com_Printf("...using ALC_EXT_EFX\n");
	}
	else
		Com_Printf("...ALC_EXT_EFX not found\n");
}

/*
 ==================
 ALW_InitDriver
 ==================
*/
static bool ALW_InitDriver (void){

	const char	*deviceName;

	Com_Printf("Initializing OpenAL driver\n");

	// Open the device
	if (!s_deviceName->value[0])
		deviceName = "Generic Software";
	else
		deviceName = s_deviceName->value;

	Com_Printf("...opening device: ");
	if ((alwState.hDevice = qalcOpenDevice(deviceName)) == NULL){
		Com_Printf("failed\n");

		// Try software device, unless we just tried it and failed
		if (!Str_ICompare(deviceName, "Generic Software"))
			return false;

		Com_Printf("...trying software device: ");
		if ((alwState.hDevice = qalcOpenDevice("Generic Software")) == NULL){
			Com_Printf("failed\n");
			return false;
		}
	}
	Com_Printf("succeeded\n");

	// Create the AL context and make it current
	Com_Printf("...creating AL context: ");
	if ((alwState.hALC = qalcCreateContext(alwState.hDevice, NULL)) == NULL){
		Com_Printf("failed\n");

		qalcCloseDevice(alwState.hDevice);
		alwState.hDevice = NULL;

		return false;
	}
	Com_Printf("succeeded\n");

	Com_Printf("...making context current: ");
	if (!qalcMakeContextCurrent(alwState.hALC)){
		Com_Printf("failed\n");

		qalcDestroyContext(alwState.hALC);
		alwState.hALC = NULL;

		qalcCloseDevice(alwState.hDevice);
		alwState.hDevice = NULL;

		return false;
	}
	Com_Printf("succeeded\n");

	return true;
}

/*
 ==================
 ALW_Init
 ==================
*/
void ALW_Init (void){

	int		majorVersion, minorVersion;
	int		attribs[64], attribsSize;
	int		i;

	Com_Printf("Initializing OpenAL subsystem\n");

	// Initialize our QAL dynamic bindings
	if (!QAL_Init(s_alDriver->value))
		Com_Error(true, "ALW_Init: could not load OpenAL subsystem");

	// Get device list
	if (qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
		alConfig.deviceList = qalcGetString(NULL, ALC_DEVICE_SPECIFIER);
	else
		alConfig.deviceList = "Generic Hardware\0Generic Software\0";

	// Initialize the device, context, etc...
	if (!ALW_InitDriver()){
		QAL_Shutdown();

		Com_Error(true, "ALW_Init: could not load OpenAL subsystem");
	}

	// We now have a working OpenAL subsystem
	alConfig.initialized = true;

	// Check for AL 1.1 support
	qalcGetIntegerv(alwState.hDevice, ALC_MAJOR_VERSION, sizeof(int), &majorVersion);
	qalcGetIntegerv(alwState.hDevice, ALC_MINOR_VERSION, sizeof(int), &minorVersion);

	if (majorVersion < 1 || (majorVersion == 1 && minorVersion < 1)){
		ALW_Shutdown();

		Com_Error(true, "The current sound card / driver combination does not support OpenAL 1.1");
	}

	// Get AL strings
	alConfig.vendorString = qalGetString(AL_VENDOR);
	alConfig.rendererString = qalGetString(AL_RENDERER);
	alConfig.versionString = qalGetString(AL_VERSION);
	alConfig.extensionsString = qalGetString(AL_EXTENSIONS);

	// Get ALC strings
	alConfig.alcExtensionsString = qalcGetString(alwState.hDevice, ALC_EXTENSIONS);

	// Get device name
	alConfig.deviceName = qalcGetString(alwState.hDevice, ALC_DEVICE_SPECIFIER);

	// Get implementation details
	qalcGetIntegerv(alwState.hDevice, ALC_ATTRIBUTES_SIZE, 1, &attribsSize);
	if (attribsSize > 0){
		qalcGetIntegerv(alwState.hDevice, ALC_ALL_ATTRIBUTES, attribsSize, attribs);

		for (i = 0; i < attribsSize - 1; i += 2){
			switch (attribs[i]){
			case ALC_FREQUENCY:
				alConfig.mixerFrequency = attribs[i+1];
				break;
			case ALC_REFRESH:
				alConfig.mixerRefresh = attribs[i+1];
				break;
			case ALC_SYNC:
				alConfig.mixerSync = attribs[i+1];
				break;
			}
		}
	}

	// Initialize extensions
	ALW_InitExtensions();

	// Enable logging if requested
	QAL_EnableLogging(s_logFile->integerValue);
}

/*
 ==================
 ALW_Shutdown
 ==================
*/
void ALW_Shutdown (void){

	if (!alConfig.initialized)
		return;

	Com_Printf("Shutting down OpenAL subsystem\n");

	if (alwState.hALC){
		if (qalcMakeContextCurrent){
			Com_Printf("...alcMakeContextCurrent( NULL ): ");
			if (!qalcMakeContextCurrent(NULL))
				Com_Printf("failed\n");
			else
				Com_Printf("succeeded\n");
		}

		if (qalcDestroyContext){
			Com_Printf("...destroying AL context\n");
			qalcDestroyContext(alwState.hALC);
		}

		alwState.hALC = NULL;
	}

	if (alwState.hDevice){
		if (qalcCloseDevice){
			Com_Printf("...closing device\n");
			qalcCloseDevice(alwState.hDevice);
		}

		alwState.hDevice = NULL;
	}

	QAL_EnableLogging(false);

	QAL_Shutdown();

	memset(&alConfig, 0, sizeof(alConfig_t));
	memset(&alwState, 0, sizeof(alwState_t));
}
